SPDX-FileCopyrightText: 2026 Pierre-Elie Mahussier & Rafael Rodrigues Braguês SPDX-FileCopyrightText: 2026 AlICe laboratory https://alicelab.be
SPDX-License-Identifier: GPL-3.0-or-later
Blender 5.0.1 - hash : a3bd93c5b259
import math
import os
import random
import bmesh
import bpy
from mathutils import VectorPARAMÈTRES #
BASS_NAMES = [
"Bass.001",
"Bass.002",
"Bass.003",
"Bass.004", # Liste des basses
"Bass.005",
"Bass.006",
"Bass.007",
"Bass.008",
]
ROW_GAP = 0.5 # l’espace entre les basses placées côte à côte
CUBE_X = 0.0 # position X du cube principal
CUBE_Y = 0.0 # position Y du cube principal
DRUMS_Y_OFFSET = 20 # décalage des drums par rapport au cube (pas celui sur les face du cube mais tout les drums du morceau)
BOOLEAN_OFFSET = 2.5 # dimension du cutter boolean
BOOLEAN_SOLVER = "FLOAT" # type de solveur boolean
DRUM_MARGIN = 0.1 # petite marge de sécurité entre les drums placés
DRUM_PAD_FACTOR = 0.07 # facteur de rembourrage qui empêche les objets d’être placés trop près des bords.
DRUM_BUFFER = 1.10 # C’est un petit buffer qui agrandit légèrement les dimensions utilisées pour vérifier les collisions
HIHAT_GAP = 1.4 # espace entre les deux parties du hi-hat
MAX_BOOLEAN_BOXES = 12 # nombre maximum de booleans appliqués pendant une exécution
CUTTER_SHIFT_FACTOR = 0.350 # facteur qui contrôle le déplacement aléatoire des cutters pour que les coupes ne soient pas toujours identiquesNETTOYAGE #
def reset_object(_obj):
bpy.ops.object.select_all(action="DESELECT")
_obj.select_set(True)
bpy.context.view_layer.objects.active = _obj
_obj.location = Vector((0.0, 0.0, 0.0))
_obj.rotation_euler = (0.0, 0.0, 0.0)
_obj.scale = Vector((1.0, 1.0, 1.332))
bpy.context.view_layer.update()
bpy.ops.object.origin_set(type="ORIGIN_GEOMETRY", center="BOUNDS")
_obj.location = Vector((0.0, 0.0, 0.0))
bpy.context.view_layer.update()def get_extents(_obj):
bpy.context.view_layer.update()
_corners = [_obj.matrix_world @ Vector(c) for c in _obj.bound_box]
_xs = [v.x for v in _corners]
_ys = [v.y for v in _corners]
_zs = [v.z for v in _corners]
return min(_xs), max(_xs), min(_ys), max(_ys), min(_zs), max(_zs)def duplicate_object(_obj, _new_name=None):
_dup = _obj.copy()
_dup.data = _obj.data.copy()
_dup.animation_data_clear()
if _new_name:
_dup.name = _new_name
_dup.data.name = _new_name + "_mesh"
bpy.context.collection.objects.link(_dup)
return _dupSupprime les objets temporaires du cube precedent
def delete_temp_objects():
_to_delete = [
_obj
for _obj in bpy.data.objects
if _obj.name.endswith("_TEMP")
or _obj.name.startswith("BOOL_")
or _obj.name.startswith("MESURE_")
]
for _obj in _to_delete:
bpy.data.objects.remove(_obj, do_unlink=True)def beat_box(_pos, _size, _name):
_outer = _size
_inner = (_size[0] * 0.7, _size[1] * 2, _size[2] * 0.8)
bpy.ops.mesh.primitive_cube_add(size=1, location=_pos, scale=_outer)
outer = bpy.context.object
outer.name = _name
outer.data.name = _name + "_mesh"
bpy.ops.mesh.primitive_cube_add(size=1, location=_pos, scale=_inner)
inner = bpy.context.object
inner.name = _name + "_cutter"
inner.data.name = _name + "_cutter_mesh"
bool_mod = outer.modifiers.new(name="Boolean_cut", type="BOOLEAN")
bool_mod.operation = "DIFFERENCE"
bool_mod.object = inner
bool_mod.solver = "FLOAT"
bpy.context.view_layer.objects.active = outer
bpy.ops.object.modifier_apply(modifier="Boolean_cut")
bpy.data.objects.remove(inner, do_unlink=True)
outer.rotation_euler[0] = math.radians(90)
return outerdef beat_triangle(_pos, _size, _name):
mesh = bpy.data.meshes.new(_name + "_mesh")
obj = bpy.data.objects.new(_name, mesh)
bpy.context.collection.objects.link(obj)
bm = bmesh.new()
x, y, z = _size
v1 = bm.verts.new((-x, 0, -z))
v2 = bm.verts.new((x, 0, -z))
v3 = bm.verts.new((0, 0, z))
v4 = bm.verts.new((-x, y, -z))
v5 = bm.verts.new((x, y, -z))
v6 = bm.verts.new((0, y, z))
bm.faces.new((v1, v2, v3))
bm.faces.new((v4, v5, v6))
bm.faces.new((v1, v2, v5, v4))
bm.faces.new((v2, v3, v6, v5))
bm.faces.new((v3, v1, v4, v6))
bm.to_mesh(mesh)
bm.free()
obj.location = _pos
obj.rotation_mode = "XYZ"
obj.rotation_euler = (0.0, 0.0, 0.0)
return objdef beat_clap(_pos, _size, _name):
bpy.ops.mesh.primitive_cube_add(
size=1,
location=_pos,
scale=(_size[0] * 0.5, _size[1] * 1.90, _size[2] * 0.20),
)
bpy.context.object.name = _name
bpy.context.object.data.name = _name + "_mesh"
bpy.context.object.rotation_euler = (0.0, 0.0, 0.0)
return bpy.context.objectdef beat_snare(_pos, _size, _name):
arm_thickness = _size[0] * 0.35
arm_length = _size[1] * 1.6
depth = _size[2] * 0.22
bpy.ops.mesh.primitive_cube_add(
size=0.8,
location=_pos,
scale=(arm_length, arm_thickness, depth),
)
rect_1 = bpy.context.object
rect_1.name = _name + "_part1"
rect_1.data.name = _name + "_part1_mesh"
bpy.ops.mesh.primitive_cube_add(
size=0.8,
location=_pos,
scale=(arm_thickness, arm_length, depth),
)
rect_2 = bpy.context.object
rect_2.name = _name + "_part2"
rect_2.data.name = _name + "_part2_mesh"
bpy.ops.object.select_all(action="DESELECT")
rect_1.select_set(True)
rect_2.select_set(True)
bpy.context.view_layer.objects.active = rect_1
bpy.ops.object.join()
snare = bpy.context.object
snare.name = _name
snare.data.name = _name + "_mesh"
return snaredef beat_hihat(_pos, _size, _name):
bpy.ops.mesh.primitive_cube_add(size=2, location=_pos, scale=_size)
r1 = bpy.context.object
r1.name = _name
r1.data.name = _name + "_mesh"
bpy.ops.mesh.primitive_cube_add(size=2, location=_pos, scale=_size)
r2 = bpy.context.object
r2.name = _name + ".001"
r2.data.name = _name + ".001_mesh"
return [r1, r2]def beat_triangle_box(_pos, _size, _name):
mesh = bpy.data.meshes.new(_name + "_mesh")
obj = bpy.data.objects.new(_name, mesh)
bpy.context.collection.objects.link(obj)
bm = bmesh.new()
x, y, z = _size
v1 = bm.verts.new((-x, 0, -z))
v2 = bm.verts.new((x, 0, -z))
v3 = bm.verts.new((0, 0, z))
v4 = bm.verts.new((-x, y, -z))
v5 = bm.verts.new((x, y, -z))
v6 = bm.verts.new((0, y, z))
bm.faces.new((v1, v2, v3))
bm.faces.new((v4, v5, v6))
bm.faces.new((v1, v2, v5, v4))
bm.faces.new((v2, v3, v6, v5))
bm.faces.new((v3, v1, v4, v6))
bm.to_mesh(mesh)
bm.free()
obj.location = _pos
return objdef beat_losange(_pos, _size, _name):
mesh = bpy.data.meshes.new(_name + "_mesh")
obj = bpy.data.objects.new(_name, mesh)
bpy.context.collection.objects.link(obj)
bm = bmesh.new()
x, y, z = _size
v1 = bm.verts.new((0, 0, z))
v2 = bm.verts.new((x, 0, 0))
v3 = bm.verts.new((0, 0, -z))
v4 = bm.verts.new((-x, 0, 0))
v5 = bm.verts.new((0, y, z))
v6 = bm.verts.new((x, y, 0))
v7 = bm.verts.new((0, y, -z))
v8 = bm.verts.new((-x, y, 0))
bm.faces.new((v1, v2, v3, v4))
bm.faces.new((v5, v6, v7, v8))
bm.faces.new((v1, v2, v6, v5))
bm.faces.new((v2, v3, v7, v6))
bm.faces.new((v3, v4, v8, v7))
bm.faces.new((v4, v1, v5, v8))
bm.to_mesh(mesh)
bm.free()
obj.location = _pos
return objOUTILS DE POSITIONNEMENT #
Calcule les coordonnées de placement des éléments des drums de tout le morceau derriere le cube
PART_WIDTH = 32def ox(x, part):
return x + part * PART_WIDTHdef oy(y):
return y + DRUMS_Y_OFFSET_original_basses = []
for _name in BASS_NAMES:
_obj = bpy.data.objects.get(_name)
if _obj and _obj.type == "MESH":
_original_basses.append(_obj)delete_temp_objects()for _obj in _original_basses:
_obj.hide_viewport = False
_obj.hide_render = False
reset_object(_obj)
_obj.hide_viewport = True
_obj.hide_render = TrueDUPLIQUER TOUTES LES BASSES EN _TEMP #
Duplique les basses originales en objets temporaires pour la creation du nouveau cube
_all_basses = []
for _orig in _original_basses:
_dup = duplicate_object(_orig, _orig.name + "_TEMP")
reset_object(_dup)
_dup.hide_viewport = True
_dup.hide_render = True
_all_basses.append(_dup)MÉLANGER TOUTES LES BASSES ET CHOISIR 3 POUR LE CUBE #
Mélange les basses et sélectionne 3 objets pour le cube
random.shuffle(_all_basses)
_selected = _all_basses[:3]
_not_selected = _all_basses[3:]_current_z = 0.0
_cube_x_min = None
_cube_x_max = None
_cube_y_min = None
_cube_y_max = None
_cube_z_min = 0.0
_cube_z_max = None
for _obj in _selected:
_obj.hide_viewport = False
_obj.hide_render = False
_rot_deg = random.choice([0, 90, 180, 270])
_obj.rotation_euler = (0.0, 0.0, math.radians(_rot_deg))
_obj.location = Vector((0.0, 0.0, 0.0))
bpy.context.view_layer.update()
_x_min, _x_max, _y_min, _y_max, _z_min, _z_max = get_extents(_obj)
_obj.location.x = CUBE_X - (_x_min + _x_max) / 2
_obj.location.y = CUBE_Y - (_y_min + _y_max) / 2
_obj.location.z = _current_z - _z_min
bpy.context.view_layer.update()
_xn, _xx, _yn, _yx, _zn, _zx = get_extents(_obj)
if _cube_x_min is None or _xn < _cube_x_min:
_cube_x_min = _xn
if _cube_x_max is None or _xx > _cube_x_max:
_cube_x_max = _xx
if _cube_y_min is None or _yn < _cube_y_min:
_cube_y_min = _yn
if _cube_y_max is None or _yx > _cube_y_max:
_cube_y_max = _yx
if _cube_z_max is None or _zx > _cube_z_max:
_cube_z_max = _zx
_current_z = _zx
_cube_width = _cube_x_max - _cube_x_min
_cube_height = _cube_z_max - _cube_z_min
DRUM_SCALE_FACTOR = _cube_width / 16_current_x = _cube_x_max + ROW_GAP * 4
for _obj in _not_selected:
_obj.hide_viewport = False
_obj.hide_render = False
_rot_deg = random.choice([0, 90, 180, 270])
_obj.rotation_euler = (0.0, 0.0, math.radians(_rot_deg))
_obj.location = Vector((0.0, 0.0, 0.0))
bpy.context.view_layer.update()
_x_min, _x_max, _y_min, _y_max, _z_min, _z_max = get_extents(_obj)
_obj.location.x = _current_x - _x_min
_obj.location.y = CUBE_Y - (_y_min + _y_max) / 2
_obj.location.z = -_z_min
bpy.context.view_layer.update()
_current_x += (_x_max - _x_min) + ROW_GAPfor _obj in [o for o in bpy.data.objects if o.name.startswith("MESURE_")]:
bpy.data.objects.remove(_obj, do_unlink=True)
S = DRUM_SCALE_FACTORdef generate_mesure(_part, _prefix):
_objs = []
if _part == 0:
for i in range(1, 5):
_objs.append(beat_box((i * 2, 0, 0), (S, S, S), f"{_prefix}_Kick{i}"))
_objs.append(
beat_triangle(
(4 * 2, 0, 0), (S * 0.45, S * 0.08, S * 0.45), f"{_prefix}_FX4"
),
)
elif _part == 1:
for i in range(1, 5):
_objs.append(beat_box((i * 2, 0, 0), (S, S, S), f"{_prefix}_Kick{i}"))
_objs.append(
beat_snare((i * 2, 0, 0), (S, S, S * 0.5), f"{_prefix}_Snare{i}")
)
_objs.append(
beat_triangle(
(4 * 2, 0, 0), (S * 0.45, S * 0.08, S * 0.45), f"{_prefix}_FX4"
),
)
elif _part == 2:
for i in range(1, 5):
_objs.append(
beat_snare((i * 2, 0, 0), (S, S, S * 0.5), f"{_prefix}_Snare{i}")
)
_objs.append(
beat_triangle(
(4 * 2, 0, 0), (S * 0.45, S * 0.08, S * 0.45), f"{_prefix}_FX4"
),
)
elif _part == 3:
for i in range(1, 5):
_objs.append(beat_box((i * 2, 0, 0), (S, S, S), f"{_prefix}_Kick{i}"))
_objs.extend(
beat_hihat(
(i * 2, 0, 0), (S * 0.15, S, S * 0.5), f"{_prefix}_Hihat{i}"
),
)
_objs.append(beat_snare((3 * 2, 0, 0), (S, S, S * 0.5), f"{_prefix}_Snare3"))
_objs.append(
beat_clap((4 * 2, 0, 0), (S * 0.5, S, S * 0.5), f"{_prefix}_Clap4")
)
_objs.append(
beat_triangle(
(4 * 2, 0, 0), (S * 0.45, S * 0.08, S * 0.45), f"{_prefix}_FX4"
),
)
elif _part == 4:
for i in range(1, 5):
_objs.extend(
beat_hihat(
(i * 2, 0, 0), (S * 0.15, S, S * 0.5), f"{_prefix}_Hihat{i}"
),
)
_objs.append(
beat_clap((3 * 2, 0, 0), (S * 0.5, S, S * 0.5), f"{_prefix}_Clap3")
)
_objs.append(beat_losange((4 * 2, 0, 0), (S, S, S * 0.5), f"{_prefix}_Crash4"))
_objs.append(
beat_triangle(
(4 * 2, 0, 0), (S * 0.45, S * 0.08, S * 0.45), f"{_prefix}_FX4"
),
)
elif _part == 5:
for i in range(1, 5):
_objs.append(beat_box((i * 2, 0, 0), (S, S, S), f"{_prefix}_Kick{i}"))
_objs.extend(
beat_hihat(
(i * 2, 0, 0), (S * 0.15, S, S * 0.5), f"{_prefix}_Hihat{i}"
),
)
_objs.append(
beat_snare((i * 2, 0, 0), (S, S, S * 0.5), f"{_prefix}_Snare{i}")
)
_objs.append(
beat_clap((3 * 2, 0, 0), (S * 0.5, S, S * 0.5), f"{_prefix}_Clap3")
)
_objs.append(beat_losange((4 * 2, 0, 0), (S, S, S * 0.5), f"{_prefix}_Crash4"))
_objs.append(
beat_triangle(
(4 * 2, 0, 0), (S * 0.45, S * 0.08, S * 0.45), f"{_prefix}_FX4"
),
)
elif _part == 6:
for i in range(1, 5):
_objs.append(beat_box((i * 2, 0, 0), (S, S, S), f"{_prefix}_Kick{i}"))
_objs.extend(
beat_hihat(
(i * 2, 0, 0), (S * 0.15, S, S * 0.5), f"{_prefix}_Hihat{i}"
),
)
_objs.append(beat_snare((2 * 2, 0, 0), (S, S, S * 0.5), f"{_prefix}_Snare2"))
_objs.append(
beat_clap((3 * 2, 0, 0), (S * 0.5, S, S * 0.5), f"{_prefix}_Clap3")
)
_objs.append(beat_losange((4 * 2, 0, 0), (S, S, S * 0.5), f"{_prefix}_Crash4"))
_objs.append(
beat_triangle(
(4 * 2, 0, 0), (S * 0.45, S * 0.08, S * 0.45), f"{_prefix}_FX4"
),
)
elif _part == 7:
for i in range(1, 5):
_objs.append(
beat_snare((i * 2, 0, 0), (S, S, S * 0.5), f"{_prefix}_Snare{i}")
)
return _objsdef _face_overlap(_a, _b, _margin=0.0):
return not (
_a[1] + _margin < _b[0]
or _a[0] - _margin > _b[1]
or _a[3] + _margin < _b[2]
or _a[2] - _margin > _b[3]
)def _world_hu_hv_front(_obj):
_ex = get_extents(_obj)
return ((_ex[1] - _ex[0]) / 2) * DRUM_BUFFER, ((_ex[5] - _ex[4]) / 2) * DRUM_BUFFERdef _world_hu_hv_side(_obj):
_ex = get_extents(_obj)
return ((_ex[3] - _ex[2]) / 2) * DRUM_BUFFER, ((_ex[5] - _ex[4]) / 2) * DRUM_BUFFERPLACER LE PARTENAIRE DU HI-HAT SI NÉCESSAIRE #
Place le second élément du hi-hat si nécessaire / deuxieme rectangle pour former ca forme de deux rectangles
def _place_hihat_partner(
_obj,
_partner,
_up,
_vp,
_placed_2d,
_face,
_gap,
_length,
_cymin,
_cymax,
_cxmin,
_cxmax,
):
bpy.context.view_layer.update()
_ex_p = get_extents(_partner)
if _face in ("front", "right"):
_pw = _ex_p[1] - _ex_p[0]
_sg = _pw * HIHAT_GAP
_partner.location.x = _up + _sg
_partner.location.z = _vp
_partner.location.y = (
_cymin - _gap + _length / 2
if _face == "front"
else _cymax + _gap - _length / 2
)
bpy.context.view_layer.update()
_ex_p2 = get_extents(_partner)
_placed_2d.append((_ex_p2[0], _ex_p2[1], _ex_p2[4], _ex_p2[5]))
else:
_pw = _ex_p[3] - _ex_p[2]
_sg = _pw * HIHAT_GAP
_partner.location.y = _up + _sg
_partner.location.z = _vp
_partner.location.x = (
_cxmin - _gap + _length / 2
if _face == "left"
else _cxmax + _gap - _length / 2
)
bpy.context.view_layer.update()
_ex_p2 = get_extents(_partner)
_placed_2d.append((_ex_p2[2], _ex_p2[3], _ex_p2[4], _ex_p2[5]))PLACER UNE MESURE SUR UNE FACE DU CUBE #
Positionne les objets d’une mesure sur une face du cube (front,left,back,right / faces)
def place_takt_on_face(_objects, _face, _cxmin, _cxmax, _cymin, _cymax, _czmin, _czmax):
_depth = _cymax - _cymin
_gap = _depth * 0.02
_margin = DRUM_MARGIN
_pad = _cube_width * DRUM_PAD_FACTOR
_placed_2d = []
_processed = set()
for _obj in _objects:
if _obj.name in _processed:
continue
_is_crash = "Crash" in _obj.name
_is_fx = "FX" in _obj.name
_length = random.uniform(_depth * 0.25, _depth * 0.95)
_is_hihat_pair = "Hihat" in _obj.name and not _obj.name.endswith(".001")
_partner = None
if _is_hihat_pair:
_partner = bpy.data.objects.get(_obj.name + ".001")
if _partner:
_processed.add(_partner.name)
_partner.rotation_euler = (0.0, 0.0, 0.0)
if _face == "front":
if "Kick" in _obj.name:
_obj.rotation_euler = (math.radians(90), 0.0, 0.0)
bpy.context.view_layer.update()
_obj.scale.z = _length / max(_obj.dimensions.z, 0.0001)
elif _is_hihat_pair:
_obj.scale.y = _length / max(_obj.dimensions.y, 0.0001)
if _partner:
_partner.scale.y = _length / max(_partner.dimensions.y, 0.0001)
elif "Hihat" in _obj.name:
_processed.add(_obj.name)
continue
elif "Clap" in _obj.name:
_obj.rotation_euler = (0.0, math.radians(90), math.radians(90))
bpy.context.view_layer.update()
_obj.scale.z = _length / max(_obj.dimensions.z, 0.0001)
elif "Snare" in _obj.name:
_obj.rotation_euler = (math.radians(90), 0.0, 0.0)
bpy.context.view_layer.update()
_obj.scale.z = _length / max(_obj.dimensions.z, 0.0001)
elif _is_fx:
_obj.rotation_mode = "XYZ"
_obj.rotation_euler = (0.0, 0.0, math.radians(0))
bpy.context.view_layer.update()
_obj.scale.y = _length / max(_obj.dimensions.y, 0.0001)
else:
_obj.scale.y = _length / max(_obj.dimensions.y, 0.0001)
bpy.context.view_layer.update()
_hu, _hv = _world_hu_hv_front(_obj)
for _ in range(150):
_up = random.uniform(_cxmin + _hu + _pad, _cxmax - _hu - _pad)
_vp = random.uniform(_czmin + _hv + _pad, _czmax - _hv - _pad)
_b = (_up - _hu, _up + _hu, _vp - _hv, _vp + _hv)
if not any(_face_overlap(_b, _x, _margin) for _x in _placed_2d):
_placed_2d.append(_b)
break
_obj.location.x = _up
_obj.location.z = _vp
if _is_crash:
_obj.location.y = _cymin - _gap
elif _is_fx:
_obj.location.y = _cymin - 0.02
else:
_obj.location.y = _cymin - _gap + _length / 2
if _is_hihat_pair and _partner:
_place_hihat_partner(
_obj,
_partner,
_up,
_vp,
_placed_2d,
"front",
_gap,
_length,
_cymin,
_cymax,
_cxmin,
_cxmax,
)
elif _face == "left":
if "Kick" in _obj.name:
_obj.rotation_euler = (math.radians(90), 0.0, math.radians(270))
bpy.context.view_layer.update()
_obj.scale.z = _length / max(_obj.dimensions.z, 0.0001)
elif _is_hihat_pair:
_obj.rotation_euler[2] = math.radians(270)
_obj.scale.y = _length / max(_obj.dimensions.y, 0.0001)
if _partner:
_partner.rotation_euler[2] = math.radians(270)
_partner.scale.y = _length / max(_partner.dimensions.y, 0.0001)
elif "Hihat" in _obj.name:
_processed.add(_obj.name)
continue
elif "Clap" in _obj.name:
_obj.rotation_euler = (
math.radians(90),
math.radians(90),
math.radians(90),
)
bpy.context.view_layer.update()
_obj.scale.z = _length / max(_obj.dimensions.z, 0.0001)
elif "Snare" in _obj.name:
_obj.rotation_euler = (math.radians(90), 0.0, math.radians(270))
bpy.context.view_layer.update()
_obj.scale.z = _length / max(_obj.dimensions.z, 0.0001)
elif _is_fx:
_obj.rotation_mode = "XYZ"
_obj.rotation_euler = (0.0, 0.0, math.radians(270))
bpy.context.view_layer.update()
_obj.scale.y = _length / max(_obj.dimensions.y, 0.0001)
else:
_obj.rotation_euler[2] = math.radians(270)
_obj.scale.y = _length / max(_obj.dimensions.y, 0.0001)
bpy.context.view_layer.update()
_hu, _hv = _world_hu_hv_side(_obj)
for _ in range(150):
_up = random.uniform(_cymin + _hu + _pad, _cymax - _hu - _pad)
_vp = random.uniform(_czmin + _hv + _pad, _czmax - _hv - _pad)
_b = (_up - _hu, _up + _hu, _vp - _hv, _vp + _hv)
if not any(_face_overlap(_b, _x, _margin) for _x in _placed_2d):
_placed_2d.append(_b)
break
_obj.location.y = _up
_obj.location.z = _vp
if _is_crash:
_obj.location.x = _cxmin - _gap
elif _is_fx:
_obj.location.x = _cxmin - 0.02
else:
_obj.location.x = _cxmin - _gap + _length / 2
if _is_hihat_pair and _partner:
_place_hihat_partner(
_obj,
_partner,
_up,
_vp,
_placed_2d,
"left",
_gap,
_length,
_cymin,
_cymax,
_cxmin,
_cxmax,
)
elif _face == "right":
if "Kick" in _obj.name:
_obj.rotation_euler = (math.radians(90), 0.0, math.radians(180))
bpy.context.view_layer.update()
_obj.scale.z = _length / max(_obj.dimensions.z, 0.0001)
elif _is_hihat_pair:
_obj.rotation_euler[2] = math.radians(180)
_obj.scale.y = _length / max(_obj.dimensions.y, 0.0001)
if _partner:
_partner.rotation_euler[2] = math.radians(180)
_partner.scale.y = _length / max(_partner.dimensions.y, 0.0001)
elif "Hihat" in _obj.name:
_processed.add(_obj.name)
continue
elif "Clap" in _obj.name:
_obj.rotation_euler = (0.0, math.radians(90), 0.0)
bpy.context.view_layer.update()
_obj.scale.z = _length / max(_obj.dimensions.z, 0.0001)
elif "Snare" in _obj.name:
_obj.rotation_euler = (math.radians(90), 0.0, math.radians(180))
bpy.context.view_layer.update()
_obj.scale.z = _length / max(_obj.dimensions.z, 0.0001)
elif _is_fx:
_obj.rotation_mode = "XYZ"
_obj.rotation_euler = (0.0, 0.0, math.radians(180))
bpy.context.view_layer.update()
_obj.scale.y = _length / max(_obj.dimensions.y, 0.0001)
else:
_obj.rotation_euler[2] = math.radians(180)
_obj.scale.y = _length / max(_obj.dimensions.y, 0.0001)
bpy.context.view_layer.update()
_hu, _hv = _world_hu_hv_front(_obj)
for _ in range(150):
_up = random.uniform(_cxmin + _hu + _pad, _cxmax - _hu - _pad)
_vp = random.uniform(_czmin + _hv + _pad, _czmax - _hv - _pad)
_b = (_up - _hu, _up + _hu, _vp - _hv, _vp + _hv)
if not any(_face_overlap(_b, _x, _margin) for _x in _placed_2d):
_placed_2d.append(_b)
break
_obj.location.x = _up
_obj.location.z = _vp
if _is_crash:
_obj.location.y = _cymax + _gap
elif _is_fx:
_obj.location.y = _cymax + 0.02
else:
_obj.location.y = _cymax + _gap - _length / 2
if _is_hihat_pair and _partner:
_place_hihat_partner(
_obj,
_partner,
_up,
_vp,
_placed_2d,
"right",
_gap,
_length,
_cymin,
_cymax,
_cxmin,
_cxmax,
)
elif _face == "back":
if "Kick" in _obj.name:
_obj.rotation_euler = (math.radians(90), 0.0, math.radians(90))
bpy.context.view_layer.update()
_obj.scale.z = _length / max(_obj.dimensions.z, 0.0001)
elif _is_hihat_pair:
_obj.rotation_euler[2] = math.radians(90)
_obj.scale.y = _length / max(_obj.dimensions.y, 0.0001)
if _partner:
_partner.rotation_euler[2] = math.radians(90)
_partner.scale.y = _length / max(_partner.dimensions.y, 0.0001)
elif "Hihat" in _obj.name:
_processed.add(_obj.name)
continue
elif "Clap" in _obj.name:
_obj.rotation_euler = (0.0, math.radians(90), 0.0)
bpy.context.view_layer.update()
_obj.scale.z = _length / max(_obj.dimensions.z, 0.0001)
elif "Snare" in _obj.name:
_obj.rotation_euler = (math.radians(90), 0.0, math.radians(90))
bpy.context.view_layer.update()
_obj.scale.z = _length / max(_obj.dimensions.z, 0.0001)
elif _is_fx:
_obj.rotation_mode = "XYZ"
_obj.rotation_euler = (0.0, 0.0, math.radians(90))
bpy.context.view_layer.update()
_obj.scale.y = _length / max(_obj.dimensions.y, 0.0001)
else:
_obj.rotation_euler[2] = math.radians(90)
_obj.scale.y = _length / max(_obj.dimensions.y, 0.0001)
bpy.context.view_layer.update()
_hu, _hv = _world_hu_hv_side(_obj)
for _ in range(150):
_up = random.uniform(_cymin + _hu + _pad, _cymax - _hu - _pad)
_vp = random.uniform(_czmin + _hv + _pad, _czmax - _hv - _pad)
_b = (_up - _hu, _up + _hu, _vp - _hv, _vp + _hv)
if not any(_face_overlap(_b, _x, _margin) for _x in _placed_2d):
_placed_2d.append(_b)
break
_obj.location.y = _up
_obj.location.z = _vp
if _is_crash:
_obj.location.x = _cxmax + _gap
elif _is_fx:
_obj.location.x = _cxmax + 0.02
else:
_obj.location.x = _cxmax + _gap - _length / 2
if _is_hihat_pair and _partner:
_place_hihat_partner(
_obj,
_partner,
_up,
_vp,
_placed_2d,
"back",
_gap,
_length,
_cymin,
_cymax,
_cxmin,
_cxmax,
)
bpy.context.view_layer.update()
_processed.add(_obj.name)_all_parts = list(range(8))
random.shuffle(_all_parts)
_mesure_part_front = _all_parts[0]
_mesure_part_left = _all_parts[1]
_mesure_part_right = _all_parts[2]
_mesure_part_back = _all_parts[3]
_mesure_objs_front = generate_mesure(
_mesure_part_front,
f"MESURE_FRONT_P{_mesure_part_front + 1}",
)
place_takt_on_face(
_mesure_objs_front,
"front",
_cube_x_min,
_cube_x_max,
_cube_y_min,
_cube_y_max,
_cube_z_min,
_cube_z_max,
)
_mesure_objs_left = generate_mesure(
_mesure_part_left, f"MESURE_LEFT_P{_mesure_part_left + 1}"
)
place_takt_on_face(
_mesure_objs_left,
"left",
_cube_x_min,
_cube_x_max,
_cube_y_min,
_cube_y_max,
_cube_z_min,
_cube_z_max,
)
_mesure_objs_right = generate_mesure(
_mesure_part_right,
f"MESURE_RIGHT_P{_mesure_part_right + 1}",
)
place_takt_on_face(
_mesure_objs_right,
"right",
_cube_x_min,
_cube_x_max,
_cube_y_min,
_cube_y_max,
_cube_z_min,
_cube_z_max,
)
_mesure_objs_back = generate_mesure(
_mesure_part_back, f"MESURE_BACK_P{_mesure_part_back + 1}"
)
place_takt_on_face(
_mesure_objs_back,
"back",
_cube_x_min,
_cube_x_max,
_cube_y_min,
_cube_y_max,
_cube_z_min,
_cube_z_max,
)
_mesure_objects = (
_mesure_objs_front + _mesure_objs_left + _mesure_objs_right + _mesure_objs_back
)_mat_mesure = bpy.data.materials.get("Material.002")
if _mat_mesure:
for _obj in _mesure_objects:
if _obj.type == "MESH":
_obj.data.materials.clear()
_obj.data.materials.append(_mat_mesure)_col_name = "Mesures"
if _col_name in bpy.data.collections:
bpy.data.collections.remove(bpy.data.collections[_col_name])
_mesure_col = bpy.data.collections.new(_col_name)
bpy.context.scene.collection.children.link(_mesure_col)
for _obj in _mesure_objects:
for _c in list(_obj.users_collection):
_c.objects.unlink(_obj)
_mesure_col.objects.link(_obj)CRÉER UN BOOLEAN À PARTIR D’UNE BOUNDING BOX #
Création objet de découpe à partir d’une bounding box
def make_bbox_cutter_from_extents(
_xmin,
_xmax,
_ymin,
_ymax,
_zmin,
_zmax,
_offset,
_cutter_name,
):
_cx = (_xmin + _xmax) / 2
_cy = (_ymin + _ymax) / 2
_cz = (_zmin + _zmax) / 2
_sx = max((_xmax - _xmin) * _offset, 0.001)
_sy = max((_ymax - _ymin) * _offset, 0.001)
_sz = max((_zmax - _zmin) * _offset, 0.001)
_shift_x = random.uniform(
-(_xmax - _xmin) * CUTTER_SHIFT_FACTOR,
(_xmax - _xmin) * CUTTER_SHIFT_FACTOR,
)
_shift_y = random.uniform(
-(_ymax - _ymin) * CUTTER_SHIFT_FACTOR,
(_ymax - _ymin) * CUTTER_SHIFT_FACTOR,
)
_shift_z = random.uniform(
-(_zmax - _zmin) * CUTTER_SHIFT_FACTOR,
(_zmax - _zmin) * CUTTER_SHIFT_FACTOR,
)
_cx += _shift_x
_cy += _shift_y
_cz += _shift_z
_hx = _sx / 2
_hy = _sy / 2
_hz = _sz / 2
_mesh = bpy.data.meshes.new(_cutter_name + "_mesh")
_obj = bpy.data.objects.new(_cutter_name, _mesh)
bpy.context.collection.objects.link(_obj)
_verts = [
(_cx - _hx, _cy - _hy, _cz - _hz),
(_cx - _hx, _cy - _hy, _cz + _hz),
(_cx - _hx, _cy + _hy, _cz - _hz),
(_cx - _hx, _cy + _hy, _cz + _hz),
(_cx + _hx, _cy - _hy, _cz - _hz),
(_cx + _hx, _cy - _hy, _cz + _hz),
(_cx + _hx, _cy + _hy, _cz - _hz),
(_cx + _hx, _cy + _hy, _cz + _hz),
]
_faces = [
(0, 4, 6, 2),
(1, 3, 7, 5),
(0, 1, 5, 4),
(2, 6, 7, 3),
(0, 2, 3, 1),
(4, 5, 7, 6),
]
_mesh.from_pydata(_verts, [], _faces)
_mesh.update()
_obj.hide_viewport = True
_obj.hide_render = True
return _objdef make_bbox_cutter(_mesure_obj, _offset, _cutter_name):
bpy.context.view_layer.update()
_ex = get_extents(_mesure_obj)
return make_bbox_cutter_from_extents(
_ex[0],
_ex[1],
_ex[2],
_ex[3],
_ex[4],
_ex[5],
_offset,
_cutter_name,
)APPLIQUER UN BOOLEAN SUR UNE BASSE #
Fonction pour appliquer une découpe booléenne sur un objet de bass
def apply_boolean_one(_bass, _cutter, _solver):
bpy.ops.object.select_all(action="DESELECT")
_bass.select_set(True)
bpy.context.view_layer.objects.active = _bass
_mod = _bass.modifiers.new(name="_bool_cut", type="BOOLEAN")
_mod.operation = "DIFFERENCE"
_mod.object = _cutter
_mod.solver = _solver
bpy.ops.object.modifier_apply(modifier=_mod.name)
if _cutter.name in bpy.data.objects:
bpy.data.objects.remove(_cutter, do_unlink=True)APPLIQUER PLUSIEURS CUTTERS SUR LES BASSES #
Applique plusieurs découpes booléennes sur les objets de basses
def apply_all_booleans(_bass_targets, _mesure_objects, _offset, _solver, _max_count):
_mesh_mesures = [o for o in _mesure_objects if o.type == "MESH"]
if len(_mesh_mesures) == 0:
return
_filtered_mesures = []
_processed_names = set()
for _obj in _mesh_mesures:
if _obj.name in _processed_names:
continue
if "Hihat" in _obj.name and _obj.name.endswith(".001"):
continue
_filtered_mesures.append(_obj)
if "Hihat" in _obj.name and not _obj.name.endswith(".001"):
_partner = bpy.data.objects.get(_obj.name + ".001")
if _partner:
_processed_names.add(_partner.name)
_processed_names.add(_obj.name)
if len(_filtered_mesures) == 0:
return
_count = min(_max_count, len(_filtered_mesures))
_chosen_mesures = random.sample(_filtered_mesures, _count)
for _mesure_obj in _chosen_mesures:
if "Hihat" in _mesure_obj.name and not _mesure_obj.name.endswith(".001"):
_partner = bpy.data.objects.get(_mesure_obj.name + ".001")
if _partner and _partner.type == "MESH":
_ex1 = get_extents(_mesure_obj)
_ex2 = get_extents(_partner)
_xmin = min(_ex1[0], _ex2[0])
_xmax = max(_ex1[1], _ex2[1])
_ymin = min(_ex1[2], _ex2[2])
_ymax = max(_ex1[3], _ex2[3])
_zmin = min(_ex1[4], _ex2[4])
_zmax = max(_ex1[5], _ex2[5])
_base_cutter = make_bbox_cutter_from_extents(
_xmin,
_xmax,
_ymin,
_ymax,
_zmin,
_zmax,
_offset,
"BOOL_" + _mesure_obj.name + "_pair",
)
else:
_base_cutter = make_bbox_cutter(
_mesure_obj,
_offset,
"BOOL_" + _mesure_obj.name,
)
else:
_base_cutter = make_bbox_cutter(
_mesure_obj, _offset, "BOOL_" + _mesure_obj.name
)
for _bass in _bass_targets:
if _bass.type != "MESH":
continue
_cutter_copy = _base_cutter.copy()
_cutter_copy.data = _base_cutter.data.copy()
_cutter_copy.name = _base_cutter.name + "_copy"
_cutter_copy.data.name = _base_cutter.name + "_copy_mesh"
bpy.context.collection.objects.link(_cutter_copy)
_cutter_copy.hide_viewport = True
_cutter_copy.hide_render = True
apply_boolean_one(_bass, _cutter_copy, _solver)
if _base_cutter.name in bpy.data.objects:
bpy.data.objects.remove(_base_cutter, do_unlink=True)
bpy.ops.object.select_all(action="DESELECT")
for _obj in list(bpy.data.objects):
if _obj.name.startswith("BOOL_"):
bpy.data.objects.remove(_obj, do_unlink=True)LANCER LES BOOLEANS SUR LES BASSES DU CUBE #
apply_all_booleans(
_bass_targets=[o for o in _selected if o.type == "MESH"],
_mesure_objects=_mesure_objects,
_offset=BOOLEAN_OFFSET,
_solver=BOOLEAN_SOLVER,
_max_count=MAX_BOOLEAN_BOXES,
)CRÉER LES DRUMS DU MORCEAU SCRIPT COMPLET SI ABSENT #
_drums_already_exist = any(
o.name.startswith(("P1_", "P2_", "P3_", "P4_", "P5_", "P6_", "P7_", "P8_"))
for o in bpy.data.objects
)
if not _drums_already_exist:
part = 0
for i in range(1, 17):
beat_box(
(ox(2 * i - 0.5, part), oy(0), 8), (0.125, 0.125, 0.125), f"P1_Kick{i}"
)
if i % 4 == 0:
beat_triangle(
(ox(2 * i - 0.5, part), oy(0), 6),
(0.125, 0.02, 0.0625),
f"P1_FX{i}",
)
part = 1
for i in range(1, 17):
beat_box(
(ox(2 * i - 0.5, part), oy(0), 8), (0.125, 0.125, 0.125), f"P2_Kick{i}"
)
if i % 4 == 0:
beat_triangle(
(ox(2 * i - 0.5, part), oy(0), 6),
(0.125, 0.02, 0.0625),
f"P2_FX{i}",
)
beat_snare(
(ox(30 + 0.5, part), oy(0), 4), (0.125, 0.125, 0.0625), "P2_Snare_extra1"
)
beat_snare(
(ox(22 + 0.5, part), oy(0), 4), (0.125, 0.125, 0.0625), "P2_Snare_extra2"
)
for i in range(1, 17):
beat_snare(
(ox(2 * i - 0.5, part), oy(0), 4), (0.125, 0.125, 0.0625), f"P2_Snare{i}"
)
part = 2
for i in range(1, 17):
if i % 4 == 0:
beat_triangle(
(ox(2 * i - 0.5, part), oy(0), 6),
(0.125, 0.02, 0.0625),
f"P3_FX{i}",
)
beat_snare(
(ox(30 + 0.5, part), oy(0), 4), (0.125, 0.125, 0.0625), "P3_Snare_extra1"
)
beat_snare(
(ox(22 + 0.5, part), oy(0), 4), (0.125, 0.125, 0.0625), "P3_Snare_extra2"
)
for i in range(1, 17):
beat_snare(
(ox(2 * i - 0.5, part), oy(0), 4), (0.125, 0.125, 0.0625), f"P3_Snare{i}"
)
part = 3
for i in range(1, 17):
beat_box(
(ox(2 * i - 0.5, part), oy(0), 8), (0.125, 0.125, 0.125), f"P4_Kick{i}"
)
if i % 4 == 0:
beat_triangle(
(ox(2 * i - 0.5, part), oy(0), 6),
(0.125, 0.02, 0.0625),
f"P4_FX{i}",
)
for i in range(1, 17):
beat_hihat(
(ox(2 * i - 0.5, part), oy(0), 4),
(0.125 * 0.15, 0.125, 0.0625),
f"P4_Hihat{i}",
)
for i in range(9, 17):
beat_snare(
(ox(2 * i - 0.5, part), oy(0), 0), (0.125, 0.125, 0.0625), f"P4_Snare{i}"
)
for i in range(4, 13):
beat_triangle_box(
(ox(2 * i, part), oy(-0.5), 2),
(0.0625, 0.125, 0.0625),
f"P4_Clap{i}",
)
part = 4
for i in range(1, 17):
if i % 4 == 0:
beat_triangle(
(ox(2 * i - 0.5, part), oy(0), 6),
(0.125, 0.02, 0.0625),
f"P5_FX{i}",
)
for i in range(1, 17):
beat_hihat(
(ox(2 * i - 0.5, part), oy(0), 4),
(0.125 * 0.15, 0.125, 0.0625),
f"P5_Hihat{i}",
)
for i in range(5, 17):
beat_box(
(ox(2 * i - 0.5, part), oy(0), 0), (0.125, 0.125, 0.0625), f"P5_Clap{i}"
)
for i in range(9, 17):
beat_losange(
(ox(2 * i, part), oy(-0.5), -2), (0.0625, 0.125, 0.0625), f"P5_Crash{i}"
)
part = 5
for i in range(1, 17):
beat_box(
(ox(2 * i - 0.5, part), oy(0), 8), (0.125, 0.125, 0.125), f"P6_Kick{i}"
)
beat_hihat(
(ox(2 * i - 0.5, part), oy(0), 4),
(0.125 * 0.15, 0.125, 0.0625),
f"P6_Hihat{i}",
)
beat_snare(
(ox(2 * i - 0.5, part), oy(0), 0), (0.125, 0.125, 0.0625), f"P6_Snare{i}"
)
beat_triangle_box(
(ox(2 * i, part), oy(-0.5), 2),
(0.0625, 0.125, 0.0625),
f"P6_Clap{i}",
)
beat_losange(
(ox(2 * i, part), oy(-0.5), -2), (0.0625, 0.125, 0.0625), f"P6_Crash{i}"
)
if i % 4 == 0:
beat_triangle(
(ox(2 * i - 0.5, part), oy(0), 6),
(0.125, 0.02, 0.0625),
f"P6_FX{i}",
)
part = 6
for i in range(1, 14):
beat_box(
(ox(2 * i - 0.5, part), oy(0), 8), (0.125, 0.125, 0.125), f"P7_Kick{i}"
)
for i in range(1, 17):
if i % 4 == 0:
beat_triangle(
(ox(2 * i - 0.5, part), oy(0), 6),
(0.125, 0.02, 0.0625),
f"P7_FX{i}",
)
for i in range(1, 17):
beat_hihat(
(ox(2 * i - 0.5, part), oy(0), 4),
(0.125 * 0.15, 0.125, 0.0625),
f"P7_Hihat{i}",
)
for i in range(1, 12):
beat_box(
(ox(2 * i - 0.5, part), oy(0), 0), (0.125, 0.125, 0.125), f"P7_Clap{i}"
)
for i in range(1, 5):
beat_triangle_box(
(ox(2 * i, part), oy(-0.5), 2),
(0.0625, 0.125, 0.0625),
f"P7_Snare{i}",
)
for i in range(1, 8):
beat_losange(
(ox(2 * i, part), oy(-0.5), -2), (0.0625, 0.125, 0.0625), f"P7_Crash{i}"
)
part = 7
beat_snare(
(ox(30 + 0.5, part), oy(0), 4), (0.125, 0.125, 0.0625), "P8_Snare_extra1"
)
beat_snare(
(ox(22 + 0.5, part), oy(0), 4), (0.125, 0.125, 0.0625), "P8_Snare_extra2"
)
for i in range(1, 17):
beat_snare(
(ox(2 * i - 0.5, part), oy(0), 4), (0.125, 0.125, 0.0625), f"P8_Snare{i}"
)bpy.ops.object.select_all(action="DESELECT")
_prefix_front = f"P{_mesure_part_front + 1}_"
_prefix_left = f"P{_mesure_part_left + 1}_"
_prefix_right = f"P{_mesure_part_right + 1}_"
_prefix_back = f"P{_mesure_part_back + 1}_"
for _obj in bpy.data.objects:
if (
_obj.name.startswith(_prefix_front)
or _obj.name.startswith(_prefix_left)
or _obj.name.startswith(_prefix_right)
or _obj.name.startswith(_prefix_back)
):
_obj.select_set(True)RÉSUMÉ FINAL #
os.system("cls")
print()
print("##################################")
print("# RÉSUMÉ FINAL #")
print("##################################")
print()
print("Basses empilées (copies temporaires) :")
for i, _obj in enumerate(_selected):
print(" [" + str(i + 1) + "] " + _obj.name)
print()
print("Ordre : bas -> haut")
print(" " + " -> ".join([o.name for o in _selected]))
print()
print("Les restants sont placés à côté :")
print(" " + " | ".join([o.name for o in _not_selected]))
print()
print("Mesures :")
print(" Front : Partie", _mesure_part_front + 1)
print(" Left : Partie", _mesure_part_left + 1)
print(" Right : Partie", _mesure_part_right + 1)
print(" Back : Partie", _mesure_part_back + 1)
print("##################################")